package hivMultiCompartment;

/**
 *  A simple model of bugs in a 2D world, going to an
 *  exogenous source of pheromone pumped into the center cell.
 */


import java.util.Iterator;
import uchicago.src.sim.engine.Schedule;



public class Model extends ModelParameters {

	// instance variables for run-time parameters
    public int              sizeLX = 48, sizeLY = 60;   // integer size of lymph node
    public int 				sizeTX = 51, sizeTY = 51; // size of Thymus
    public int 				CD4TRecruitInterval = 10;
    public int 				CD8TRecruitInterval = 10;
    public int 				BRecruitInterval = 20;
    public int 				APCRecruitInterval = 10;
    //virus
    public int 				virusInputStart = 1000;
    public int 				virusInputEnd = 1500;
    public int 				virusInputInterval = 100;
    public int 				virusInputLoad = 1000;
    public int 				virusStrainNr = 30;
    //log scaled
    public double 			virusMutationRate = -2;
    public double			virusReactivateRate = -4;
    //protease inhibitor: inhibit assembly and release
    public int 				proteaseInhibitorStart = 2;
    public int 				proteaseInhibitorEnd = 1;
    public int 				proteaseInhibitorInterval = 10;
    public int				proteaseInhibitorDuration = 7;
    public double 			proteaseInhibitorRatio = 0.1;
    //reverse transcriptase inhibitor: inhibit infection
    public int 				rtInhibitorStart = 2;
    public int 				rtInhibitorEnd = 1;
    public int 				rtInhibitorInterval = 30;
    public int 				rtInhibitorDuration = 15;
    public double 			rtInhibitorRatio = 0.1;
    
    
    // Parameters not varied
    public double			diffusionK = 0.1; // antibody diffusion rate
    public double 			degenRate = 0.98; //antibody degenaration rate
    
	// instance variables for organs
    public Organ2DSpace		lymphNode;
    public Organ2DSpace		thymus;
    public OrganNonSpatial	bloodStream;

	// color map transformation parameters
	public static int				colorMapSize = 256;

	// an iv used by repast for keeping track of model steps
	public Schedule   		schedule;			// repast schedule of events

	/////////////////////////////////////////////////////////////////////////////
	// To add a new parameter that can be set at run time (via command line or gui)
	// do the following steps:
	// 0. Add an instance variable (field) for the parameter to Model.java .
	// 1. In the method addModelSpecificParameters
	//    add alias and long name for Model parameters you want to set at run time
	//    the long name should be same as instance variable
	// 2. Add setter and getter methods for the parameter
	// 3. In the method getInitParam, add the parameter name to the array of Strings.
	// 4. Compile and test by running model and seeing that the parameter:
	//    - is on the GUI panel, in the report file, and settable on the command line,
	//      eg     bin/batchrun.sh newParName=1.4
	// 5. if this parameter is suppose to influence parameters in GlobalConstants class,
	//   add to setupParameterValue() or updateParameterValue(), depending on whether they 
	//   are changed only once at the beginning, or are updated constantly. 
	// Note: the generic parameters from ModelParameters are already available.
	//       enter    bin/guirun.sh -h  
	// to see all parameters and defaults, including new ones you add.

	public void addModelSpecificParameters () {
		parametersMap.put( "LX", "sizeLX" );
		parametersMap.put( "LY", "sizeLY" );
		parametersMap.put("TX", "sizeTX");
		parametersMap.put("TY", "sizeTY");
		parametersMap.put("Thintvl", "CD4TRecruitInterval");
		parametersMap.put("Tkintvl", "CD8TRecruitInterval");
		parametersMap.put("Bintvl", "BRecruitInterval");
		parametersMap.put("APCintvl", "APCRecruitInterval");
		parametersMap.put("vStart","virusInputStart");
		parametersMap.put("vEnd","virusInputEnd");
		parametersMap.put("vIntvl","virusInputInterval");
		parametersMap.put("vLoad","virusInputLoad");
		parametersMap.put("vStrain", "virusStrainNr");
		parametersMap.put("vMRate", "virusMutationRate");
		parametersMap.put("vRRate", "virusReactivateRate");
		parametersMap.put("pIStart","proteaseInhibitorStart");
		parametersMap.put("pIEnd","proteaseInhibitorEnd");
		parametersMap.put("pIIntvl","proteaseInhibitorInterval");
		parametersMap.put("pIDur","proteaseInhibitorDuration");
		parametersMap.put("pIRatio","proteaseInhibitorRatio");
		parametersMap.put("rtIStart","rtInhibitorStart");
		parametersMap.put("rtIEnd","rtInhibitorEnd");
		parametersMap.put("rtIIntvl","rtInhibitorInterval");
		parametersMap.put("rtIDur","rtInhibitorDuration");
		parametersMap.put("rtIRatio","rtInhibitorRatio");
		
	}

	// Specify what appears in the repast parameter panel
	public String[] getInitParam () {
		String[] params = { "sizeLX", "sizeLY","sizeTX","sizeTY",
				"CD4TRecruitInterval","CD8TRecruitInterval","BRecruitInterval","APCRecruitInterval",
				"virusInputStart","virusInputEnd","virusInputInterval","virusInputLoad",
				"virusStrainNr","virusMutationRate","virusReactivateRate",
				"proteaseInhibitorStart", "proteaseInhibitorEnd", "proteaseInhibitorInterval","proteaseInhibitorRatio","proteaseInhibitorDuration",
				"rtInhibitorStart", "rtInhibitorEnd", "rtInhibitorInterval","rtInhibitorRatio","rtInhibitorDuration",
				// these are from the super class:
				"rDebug", "seed" };
		return params;
	}

	/////////////////////////////////////////////////////////////////////////////
	// setters and getters 
	// These must be defined to be able to set parameters via GUI and run command 
	//
	// NB: some things can't be changed after run starts (eg sizeX,sizeY)
	public int getSizeLX () { return sizeLX; }
	public void setSizeLX ( int szX ) { 
		sizeLX = szX; 
	}
	public int getSizeLY () { return sizeLY; }
	public void setSizeLY ( int szY ) { 
		sizeLY = szY;  
	}
	
	public int getSizeTX () { return sizeTX; }
	public void setSizeTX ( int szX ) { 
		sizeTX = szX; 
	}
	public int getSizeTY () { return sizeTY; }
	public void setSizeTY ( int szY ) { 
		sizeTY = szY;  
	}
	public int getCD4TRecruitInterval () { return CD4TRecruitInterval;}
	public void setCD4TRecruitInterval (int intvl) {
		CD4TRecruitInterval = intvl;
	}
	public int getCD8TRecruitInterval () { return CD8TRecruitInterval;}
	public void setCD8TRecruitInterval (int intvl) {
		CD8TRecruitInterval = intvl;
	}
	public int getBRecruitInterval () { return BRecruitInterval;}
	public void setBRecruitInterval (int intvl) {
		BRecruitInterval = intvl;
	}
	public int getAPCRecruitInterval () { return APCRecruitInterval;}
	public void setAPCRecruitInterval (int intvl) {
		APCRecruitInterval = intvl;
	}
	public int getVirusInputStart () { return virusInputStart;}
	public void setVirusInputStart (int s) {
		virusInputStart = s;
	}
	public int getVirusInputEnd () { return virusInputEnd;}
	public void setVirusInputEnd (int e) {
		virusInputEnd = e;
	}
	public int getVirusInputInterval() { return virusInputInterval;}
	public void setVirusInputInterval(int i) {
		virusInputInterval = i;
	}
	public int getVirusInputLoad () { return virusInputLoad;}
	public void setVirusInputLoad (int l) {
		virusInputLoad = l;
	}
	
	public int getVirusStrainNr () { return virusStrainNr;}
	public void setVirusStrainNr(int s) {
		virusStrainNr = s;
	}
	public double getVirusMutationRate () { return virusMutationRate;}
	public void setVirusMutationRate(double m) {
		virusMutationRate = m;
	}
	public double getVirusReactivateRate () { return virusReactivateRate;}
	public void setVirusReactivateRate(double r) {
		virusReactivateRate = r;
	}
	
	public int getProteaseInhibitorStart () { return proteaseInhibitorStart;}
	public void setProteaseInhibitorStart (int s) {
		proteaseInhibitorStart = s;
	}
	public int getProteaseInhibitorEnd () { return proteaseInhibitorEnd;}
	public void setProteaseInhibitorEnd (int e) {
		proteaseInhibitorEnd = e;
	}
	public int getProteaseInhibitorInterval () { return proteaseInhibitorInterval;}
	public void setProteaseInhibitorInterval (int i) {
		proteaseInhibitorInterval = i;
	}
	public int getProteaseInhibitorDuration () { return proteaseInhibitorDuration;}
	public void setProteaseInhibitorDuration (int d) {
		proteaseInhibitorDuration = d;
	}
	public double getProteaseInhibitorRatio () { return proteaseInhibitorRatio;}
	public void setProteaseInhibitorRatio (double r) {
		proteaseInhibitorRatio = r;
	}
	
	public int getRtInhibitorStart () { return rtInhibitorStart;}
	public void setRtInhibitorStart (int s) {
		rtInhibitorStart = s;
	}
	public int getRtInhibitorEnd () { return rtInhibitorEnd;}
	public void setRtInhibitorEnd (int e) {
		rtInhibitorEnd = e;
	}
	public int getRtInhibitorInterval () { return rtInhibitorInterval;}
	public void setRtInhibitorInterval (int i) {
		rtInhibitorInterval = i;
	}
	public int getRtInhibitorDuration () { return rtInhibitorDuration;}
	public void setRtInhibitorDuration (int d) {
		rtInhibitorDuration = d;
	}
	public double getRtInhibitorRatio () { return rtInhibitorRatio;}
	public void setRtInhibitorRatio (double r) {
		rtInhibitorRatio = r;
	}
	
//	public int get () { return ;}
//	public void set(int ) {
//		 = ;
//	}
	
	
	////////////////////////////////////////////////////////////////////////////////
	//parameter processing
	//
	
	////////////////////////////////////////////////////////////////////////////////
	//statics
	//
	//
	//getters for cell numbers
	public int getCellNr(OrganGeneral organ){
		return organ.cellList.size();
	}
	
	public int getVirusNr(OrganGeneral organ){
		return organ.virusList.size();
	}
	
	
	public int getCellTypeNr(OrganGeneral organ, Class type){
		int counter = 0;
		Iterator<Cell> cIter = organ.cellList.iterator();
		while (cIter.hasNext()) {
			Cell c = cIter.next();
			if(type.isInstance(c)) {
				++counter;
			}
		}
		return counter;
	}

	////////////////////////////////////////////////////////////////////////////////
	//routines
	//
	/**
	// userSetup()
	// called when user presses the "reset" button.
	// discard any existing model parts, and re-initialize as desired.
	//
	// NB: if you want values entered via the GUI to remain after restart,
	//     do not initialize them here.
	*/
	public void userSetup() {
		if ( rDebug > 0 )
			System.out.printf( "==> userSetup...\n" );

		lymphNode = null;
		thymus = null;
		bloodStream = null;
		//reset seed
		long s = System.currentTimeMillis();
		setSeed( s );
		
		
	}

	/**
	// userBuildModel
	// called when model initialized, eg, with Initialize button.
	// create all the objects that constitute the model:
	// - a 2D world
	// - a list of ants, placed at random in the world
	*/
	public void userBuildModel () {
		if ( rDebug > 0 )
			System.out.printf( "==> userBuildModel...\n" );
		
		//update Global parameters
		setupParameterValue();
		
		AgentOnGrid.setModel(this);
		
		lymphNode = new Organ2DSpace (sizeLX, sizeLY, this, diffusionK, degenRate);
		thymus = new Organ2DSpace (sizeTX, sizeTY, this, diffusionK, degenRate);
		bloodStream = new OrganNonSpatial(this);
				

		initiatePorts();
		initiateCells();
		
		if ( rDebug > 0 )
			System.out.printf( "<==  userbuildModel done.\n" );
	}

	/**
	 * 
	 */
	public void step () {
		if ( rDebug > 0 )
			System.out.printf( "==> Model step %.0f:\n", getTickCount());
		
		updateParameterValue(); //uncomment this if need to change parameters during simulation
		
		//System.out.println(getCellTypeNr(bloodStream, CD4TCell.class));
		
		tryAddVirus();
		
		recruitCells();
		//System.out.println("virus in Thymus: "+thymus.virusList.size());
		lymphNode.step();
		thymus.step();
		bloodStream.step();
		
		
		stepReport();
		//System.out.printf("tick: %.0f, release: %d,entry: %.3f\n", getTickCount(), GlobalConstants.avgReleaseCount, GlobalConstants.probEnterCell);
		//System.out.println("bloodStream number: " + bloodStream.cellList.size());
	}

	/**
	// stepReport
	// called each model time step to write out lines that look like: 
    //     timeStep  ...data...data...data...
	// first it calls a method to calculate stats to be written.
	*/
	public void stepReport () {
		if ( rDebug > 0 )
			System.out.printf( "==> Model stepReport %.0f:\n", getTickCount() );
		
		//calcStats();
		
		if ( getTickCount() %  reportFrequency == 0 ) {
		

			// set up a string with the values to write -- start with time step
			String s = String.format( "%5.0f  ", getTickCount() );

			// Append to String s here to write other data to report lines:

			s += String.format( " %5d ", getCellTypeNr(bloodStream, CD4TCell.class));
			s += String.format( " %5d ", bloodStream.virusList.size()  );
			s += String.format( " %5d ", lymphNode.cellList.size()  );
			s += String.format( " %5d ", thymus.cellList.size()  );

			// write it to the plain text report file, 'flush' buffer to file
			writeLineToPlaintextReportFile( s );
			getPlaintextReportFile().flush();
			//System.out.printf("t: %.0f; bv: %d\n", getTickCount(), bloodStream.virusList.size());
		}

	}
	///////////////////////////////////////////////////////////////////////////////////////////////////////////
	//supporting functions
	//
	//
	/**
	 * overwrite global paramter values with input at the beginning of simulation
	 */
	
	public void setupParameterValue(){
		GlobalConstants.recruitIntervalCD4T = CD4TRecruitInterval;
		GlobalConstants.recruitIntervalCD8T = CD8TRecruitInterval;
		GlobalConstants.recruitIntervalB = BRecruitInterval;
		GlobalConstants.recruitIntervalAPC = APCRecruitInterval;
		
		GlobalConstants.maxStrains = virusStrainNr;
		GlobalConstants.virusMutationRate = Math.pow(10, virusMutationRate);
		GlobalConstants.reactivationRateCD4TResting = Math.pow(10,virusReactivateRate);
		
	}
	
	static boolean pIModified = false;
	static boolean rtIModified = false;
	/**
	 * update parameter value during simulation
	 */
	public void updateParameterValue(){
		
		//protease inhibitor:
		if(timeForEvent(proteaseInhibitorStart, proteaseInhibitorEnd, proteaseInhibitorInterval, proteaseInhibitorDuration)) {
			if (!pIModified) {
				GlobalConstants.avgReleaseCount = (int)(proteaseInhibitorRatio * GlobalConstants.avgReleaseCount);
				pIModified = true;
			}
		}else if (pIModified){
			GlobalConstants.avgReleaseCount = GlobalConstants.avgReleaseCountOriginal;
			pIModified = false;
		}

		//reverse transcriptase inhibitor:
		if(timeForEvent(rtInhibitorStart, rtInhibitorEnd, rtInhibitorInterval, rtInhibitorDuration) ) {
			if(rtIModified){
				GlobalConstants.probEnterCell = rtInhibitorRatio * GlobalConstants.probEnterCell;
				rtIModified = true;
			}
		}else if (rtIModified){
			GlobalConstants.probEnterCell = GlobalConstants.probEnterCellOriginal;
			rtIModified = false;
		}
	}
	/**
	 * perform the recruitment in every time step
	 */
	public void recruitCells(){
		if (((int)getTickCount()) % GlobalConstants.recruitIntervalAPC == 0) {
			int nrRecruit = GlobalConstants.sampleNormal(GlobalConstants.recruitAPCCount);
			for (int i = 0; i < nrRecruit; ++i){
				APCell c = (APCell)createNewMobleObjectAt(APCell.class, true, lymphNode, true);
				c.matureOnRecruit(bloodStream.virusList.size());
			}
		}
		
		if (((int)getTickCount()) % GlobalConstants.recruitIntervalCD4T == 0) {
			int nrRecruit = GlobalConstants.sampleNormal(GlobalConstants.recruitCD4TCount);
			for (int i = 0; i < nrRecruit; ++i){
				if (thymusFunction()){
					createNewMobleObjectAt(CD4TCell.class, true, thymus, true);
				}
			}
		}
		if (((int)getTickCount()) % GlobalConstants.recruitIntervalCD8T == 0) {
			int nrRecruit = GlobalConstants.sampleNormal(GlobalConstants.recruitCD8TCount);
			for (int i = 0; i < nrRecruit; ++i){
				if (thymusFunction()){
					createNewMobleObjectAt(CD8TCell.class, true, thymus, true);
				}
			}
		}
		if (((int)getTickCount()) % GlobalConstants.recruitIntervalB == 0) {
			int nrRecruit = GlobalConstants.sampleNormal(GlobalConstants.recruitBCount);
			for (int i = 0; i < nrRecruit; ++i){
				createNewMobleObjectAt(BCell.class, true, bloodStream, true);
			}
		}
	}
	
	/**
	 * initiate ports in all organs
	 */
	public void initiatePorts() {
		//lymph node
		for(int i = 0; i< sizeLX; ++i){
			if (i % 6 == 1){
				createDualPortAt(i, 5, lymphNode, bloodStream, true, true, true, true, false, false, true);
				createDualPortAt(i+3, 10, lymphNode, bloodStream, true, true, true, true, false, false, true);
				
				createExitPortAt(i+3, sizeLY - 5, lymphNode, bloodStream, true, true, true);
			}
		}
		
		//thymus
		for(int i=0; i<sizeTX; ++i){
			for(int j=0; j< sizeTY; ++j) {
				if((i+7*j+6)%50 == 1) {
					//
					if((i - sizeTX/2)*(i - sizeTX/2)+(j - sizeTY/2)*(j - sizeTY/2) < (sizeTX*sizeTX + sizeTY*sizeTY)/64 ){
						createDualPortAt(i, j, thymus, bloodStream, false, false, false, true, true, true, true);
						//createEntryPortAt(i, j, thymus, true, true, true, true);
					}else {
						createDualPortAt(i, j, thymus, bloodStream, true, true, true, true, false, false, true);
					}
				}
			}
		}
		
		//initialize 
		lymphNode.initializeInportLocations();
		thymus.initializeInportLocations();
	}
	
	/**
	 * populate the grid with initial cells
	 */
	public void initiateCells() {
		//int count = GlobalConstants.recruitCD4TCount * GlobalConstants.avgLifeCD4TNaive / GlobalConstants.recruitIntervalCD4T;
		int count = 400;
		Cell c;
		for(int i = 0; i< count; ++i){
			c = (Cell)createNewMobleObjectAt(CD4TCell.class, true, bloodStream, false);
			double r =  Model.getUniformDoubleFromTo(0, 1);
			c.resetLife((int)(c.life * r));
			c.proliferateCD = (int)(c.proliferateCD*r);
		}
		count = 200;
		//count = GlobalConstants.recruitCD8TCount * GlobalConstants.avgLifeCD8TNaive / GlobalConstants.recruitIntervalCD8T;
		for(int i = 0; i< count; ++i){
			c = (Cell)createNewMobleObjectAt(CD8TCell.class, true, bloodStream, false);
			double r =  Model.getUniformDoubleFromTo(0, 1);
			c.resetLife((int)(c.life * r));
			c.proliferateCD = (int)(c.proliferateCD*r);
		}
		count = GlobalConstants.recruitBCount * GlobalConstants.avgLifeBNaive / GlobalConstants.recruitIntervalB;
		for(int i = 0; i< count; ++i){
			c = (Cell)createNewMobleObjectAt(BCell.class, true, bloodStream, false);
			double r =  Model.getUniformDoubleFromTo(0, 1);
			c.resetLife((int)(c.life * r));
		}
	}
	
	/**
	 * try to add virus to the blood
	 */
	public void tryAddVirus(){
		if(timeForEvent(virusInputStart, virusInputEnd, virusInputInterval)) {
			for(int i = 0; i < virusInputLoad; ++i){
				createNewMobleObjectAt(Virus.class, false, bloodStream, false);
			}
		}
	}
	
	/**
	 * populate initial cells in the organ.
	 * @param nrCells
	 * @param organ
	 * @return
	 */
	public boolean populateCellsInSpatialOrgan (int nrCells,Class type, Organ2DSpace organ){
		boolean result = true;
		int xSize = organ.getSizeX();
		int ySize = organ.getSizeY();
		double probCell = ((double)nrCells)/ ( xSize * ySize); 
		for (int i = 0; i < xSize; ++i) {
			for (int j = 0; j < ySize; ++j ) {
				if (organ.cellSpace.getObjectAt(i, j) == null) {
					double r = Model.getUniformDoubleFromTo(0.0,1.0);
					if (r < probCell) {
						result &= createNewMobleObjectAt(type, i, j, true, organ);
					}
				}
			}
		}
		return result;
	}
	
	/**
	 * populate cells in blood stream
	 * @param nrCells
	 * @return
	 */
	public boolean populateCellsInBloodStream(int nrCells, Class type) {
		boolean result = true;
		for (int i = 0; i < nrCells; ++i) {
			createNewMobleObjectAt(type, true, bloodStream, false);
		}
		return result;
	}
	
	/**
	 * create object in the specified organ (works for Fixed Objects)
	 * @param type
	 * @param organSource
	 * @return
	 */
	public AgentOnGrid createNewObjectAt(Class type, OrganGeneral organ) {
		AgentOnGrid agt = agentFactory(type);
		agt.resetOrgan(organ);
		return agt;
	}
	
	/**
	 * create the agent and put it in input queue (for initialization of blood compartment, or recruitment)
	 * @param type
	 * @param isCell whether this object is a cell or virus
	 * @param organ
	 * @param isRecruitment: if true, add to recruitment list instead of cell list
	 * @return
	 */
	public MobileObject createNewMobleObjectAt(Class type, boolean isCell ,OrganGeneral organ, boolean isRecruitment) {
		MobileObject obj = (MobileObject)createNewObjectAt(type, organ);
		//add to input list
		if(isCell) {
			if(isRecruitment){
				organ.recruitCellList.add((Cell)obj);
			}else {
				organ.inputCellsList.add((Cell)obj);
			}
		}
		else {
			organ.inputVirusList.add((Virus)obj);
		}
		return obj;
	}
	/**
	 * create the agent and put it onto the grid (directly put on grid. for initialization and testing)
	 * @param type
	 * @param x
	 * @param y
	 * @param isCell
	 * @param organ
	 * @return
	 */
	public boolean createNewMobleObjectAt(Class type, int x, int y, boolean isCell,Organ2DSpace organ) {
		boolean result = false;
		MobileObject obj = (MobileObject)createNewObjectAt(type, organ);
		
		//add to cell list and Grid
		if(isCell) {
			result = organ.cellSpace.addObjectAt(x, y, obj);
			organ.cellList.add((Cell)obj);
			((Cell)obj).updateMyList(organ.cellList);
		}
		else {
			result = organ.virusSpace.addObjectAt(x, y, obj);
			organ.virusList.add((Virus)obj);
			((Virus)obj).updateMyList(organ.virusList);
		}
		
		return result;
	}
	
	
	/**
	 * create a port and return the object
	 * @param type
	 * @param organSource
	 * @return
	 */
	public FixedObject createPortAt(Class type, Organ2DSpace organSource) {
		FixedObject obj = (FixedObject)createNewObjectAt(type, organSource);
		organSource.allPorts.add(obj);
		return obj;
	}
	
	/**
	 * There is no target list for entry ports.
	 * create the ports, add to input port list, add to grid, and set accessibility
	 * @param x
	 * @param y
	 * @param organSource
	 * @param st
	 * @param sb
	 * @param sa
	 * @param sv
	 * @return
	 */
	public boolean createEntryPortAt(int x, int y, Organ2DSpace organSource, boolean it, boolean ib, boolean ia, boolean iv) {
		boolean result = true;
		PortEntry obj = (PortEntry)createPortAt(PortEntry.class, organSource);
		result &= organSource.cellSpace.addObjectAt(x, y, obj);
		//input port specific:
		organSource.inputPorts.add(obj);
		obj.setEntry(it, ib, ia, iv);
		return result;
	}
	/**
	 * create an exit port
	 * @param x
	 * @param y
	 * @param organSource
	 * @param tc
	 * @param tv
	 * @param ot
	 * @param ob
	 * @param ov
	 * @return
	 */
	public boolean createExitPortAt(int x, int y, Organ2DSpace organSource, OrganGeneral to, boolean ot, boolean ob, boolean ov) {
		boolean result = true;
		PortExit obj = (PortExit)createPortAt(PortExit.class, organSource);
		result &= organSource.cellSpace.addObjectAt(x, y, obj);
		//output port specific
		obj.setTargetOrgan(to);
		obj.setExit(ot, ob, ov);
		return result;
	}
	/**
	 * create a dual ports
	 * @param x
	 * @param y
	 * @param organSource
	 * @param tc
	 * @param tv
	 * @param it
	 * @param ib
	 * @param ia
	 * @param iv
	 * @param ot
	 * @param ob
	 * @param ov
	 * @return
	 */
	public boolean createDualPortAt(int x, int y, Organ2DSpace organSource, OrganGeneral to, boolean it, boolean ib, boolean ia, boolean iv, boolean ot, boolean ob, boolean ov) {
		boolean result = true;
		PortDual obj = (PortDual)createPortAt(PortDual.class, organSource);
		result &= organSource.cellSpace.addObjectAt(x, y, obj);
		obj.setX(x);
		obj.setY(y);
		//dual port specific
		organSource.inputPorts.add(obj);
		obj.setEntry(it, ib, ia, iv);
		obj.setTargetOrgan(to);
		obj.setExit(ot, ob, ov);
		
		return result;
	}
	/**
	 * create one agent of specified type
	 * @param type
	 * @return
	 */
	public static AgentOnGrid agentFactory (Class type) {
		AgentOnGrid newAgent = null;
		if (type == CD4TCell.class) {
			newAgent = new CD4TCell();
		}
		else if (type == CD8TCell.class) {
			newAgent = new CD8TCell();
		}
		else if (type == BCell.class) {
			newAgent = new BCell();
		}
		else if (type == APCell.class) {
			newAgent = new APCell();
		}
		else if (type == Virus.class) {
			newAgent = new Virus();
		}
		else if (type == PortEntry.class) {
			newAgent = new PortEntry();
		}
		else if (type == PortExit.class) {
			newAgent = new PortExit();
		}
		else if (type == PortDual.class) {
			newAgent = new PortDual();
		}
		else{
			System.out.println("created type not known");
		}
		return newAgent;
	}
	
	/**
	 * return true if it is right time for event.
	 * @param tStart
	 * @param tEnd if tEnd is 0, never ends.
	 * @param tIntvl
	 * @return
	 */
	public boolean timeForEvent(int tStart, int tEnd, int tIntvl) {
		boolean result = false;
		int t = (int)getTickCount();
		if(t >= tStart) {
			if(t < tEnd || tEnd == 0) {
				if((t-tStart) % tIntvl == 0) {
					result = true;
				}
			}
		}
		return result;
	}
	
	/**
	 * 
	 * @param tStart
	 * @param tEnd
	 * @param tIntvl periodical event
	 * @param duration first d steps in a period return true;
	 * @return
	 */
	public boolean timeForEvent(int tStart, int tEnd, int tIntvl, int duration) {
		boolean result = false;
		int t = (int)getTickCount();
		if(t >= tStart) {
			if(t < tEnd || tEnd == 0) {
				if((t-tStart) % tIntvl < duration) {
					result = true;
				}
			}
		}
		return result;
	}
	
	
	public boolean thymusFunction() {
		boolean cellProduced = true;
		int vLoad = thymus.virusList.size();
		double e = 1.0 * vLoad / (vLoad + GlobalConstants.halfImpairVirusLoad);
		System.out.println(e);
		double r = Model.getUniformIntFromTo(0, 1);
		if(r < e) {
			cellProduced = false;
		}
		return cellProduced;
	}
	
	
	public void stepCheck() {
		Iterator<Virus> vIter = bloodStream.virusList.iterator();
		while(vIter.hasNext()){
			Virus v = vIter.next();
			if(v.Organ2D != null) System.out.println("organ not match");
		}
	}
	/**
	// printBugs
	// print some info about bugs.
	// NOTE: we sort them first, by distance to source.
	*/
	//@SuppressWarnings("unchecked") 
//	public void printBugs ( ) {
//		// sort the bugs based on distance to source
//		Collections.sort( antList, 
//			  (java.util.Comparator<? super Ant>) new BugDistanceToSourceComparator() );
// 		//Collections.sort( antList, 
//		//      (java.util.Comparator<? super Ant>) new BugWeightComparator() );
//
//		System.out.printf( "\n antList:\n" );
//		System.out.printf( "   ID     X   Y   DistToSource    prRndMove\n" ); 
//		for ( Ant aBug: antList ) {
//			System.out.printf( "  %3d   %3d %3d   %.3f         %.2f\n",
//			   	   aBug.getId(), aBug.getX(), aBug.getY(), 
//			   	   calcDistanceToSource(aBug), aBug.getProbRandMove() );
//		}
//		calcStats();  // just in case...
//		System.out.printf( " avgDistToSource: %.3f\n", antPopAvgDistanceFromSource );
//		System.out.printf( "\n" );
//	}


	///////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////
	// Comparators for sorting
	//

    /**
	// BugDistanceToSourceComparator
	// Callback for sort, to order by ascending distance to source of pheromone
	// for any pair of objects a and b to be sorted:
	// return +1  if a should sort after b, 
	//         0  if a == b,
	//        -1  if a should sort before b
	 * Note this could be cleaned up to accept some set of objects wider than Ant,
	 * eg we could define an interface that guarantees getDistanceToSource() will exist.
	*/
//	private class BugDistanceToSourceComparator implements Comparator<Ant> {
//		public  int compare ( Ant bug1, Ant bug2 ) {
//			double d1 = ( (Ant) bug1 ).getDistanceToSource();
//			double d2 = ( (Ant) bug2 ).getDistanceToSource();
//			if ( d1 > d2 )    // d2 smaller, so should be before d1 (ascending order)
//				return 1;
//			else if ( d1 < d2 )
//				return -1;
//			return 0;
//		}
//	}

	///////////////////////////////////////////////////////////////////////////////
	// writeHeaderCommentsToReportFile
	// customize to match what you are writing to the report files in stepReport.
	
	public void writeHeaderCommentsToReportFile () {
		writeLineToPlaintextReportFile( "#                    " );
		writeLineToPlaintextReportFile( "#         Blood  Blood   LN   Thymus " );
		writeLineToPlaintextReportFile( "# time    CD4T   Virus   Cell  Cell  " );
	}

	//////////////////////////////////////////////////////////////////////////////////
	// printProjectHelp
	// this should be filled in with some help to get from running as
	//        bin/guirun.sh -h
	//
	
	public void printProjectHelp() {
		System.out.printf( "\n%s -- hiv-multicompartment \n", getName() );

		printParametersMap();

		System.out.printf( "\n" );

		printForGSDrone();
		
		System.exit( 0 );

	}



	///////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////
	//
	//            USUALLY NO NEED TO CHANGE THINGS BELOW HERE
	//
	///////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////



	////////////////////////////////////////////////////////////////////////////
	// constructor, if need to do anything special.
	public Model () {
	}

	///////////////////////////////////////////////////////////////////////////
	// setup
	// set generic defaults after a run start or restart
	// calls userSetup() which the model-author defines
	//   for the specific model.

	public void setup () {
		schedule = null;
		if ( rDebug > 1 )
			System.out.printf( "==> Model-setup...\n" );

		userSetup();

		System.gc ();   // garabage collection of discarded objects
		super.setup();  // THIS SHOULD BE CALLED after setting defaults in setup().
		schedule = new Schedule (1);  // create AFTER calling super.setup()

		if ( rDebug > 1 )
			System.out.printf( "\n<=== Model-setup() done.\n" );

	}

	///////////////////////////////////////////////////////////////////////////
	// buildModel
	// build the generic "architecture" for the model,
	// and call userBuildModel() which the model-author defines
	// to create the model-specific components.

	public void buildModel () {
		if ( rDebug > 1 )
			System.out.printf( "==> buildModel...\n" );

		// CALL FIRST -- defined in super class -- it starts RNG, etc
		buildModelStart();

		userBuildModel();

		// some post-load finishing touches
		startReportFile();

		// you probably don't want to remove any of the following
		// calls to process parameter changes and write the
		// initial state to the report file.
		// NB -> you might remove/add more agentChange processing
        applyAnyStoredChanges();
        stepReport();
        getReportFile().flush();
        getPlaintextReportFile().flush();

		if ( rDebug > 1 )
			System.out.printf( "<== buildModel done.\n" );
	}




	//////////////////////////////////////////////////////////////////////////////
	public Schedule getSchedule () {	return schedule; }

	public String getName () { return "Model"; }



	/////////////////////////////////////////////////////////////////////////////
	// processEndOfRun
	// called once, at end of run.
	// writes some final info, closes report files, etc.
	public void processEndOfRun ( ) {
		if ( rDebug > 0 )  
			System.out.printf("\n\n===== processEndOfRun =====\n\n" );
		applyAnyStoredChanges();
		endReportFile();

		this.fireStopSim();
	}

}
